package fatworm.tx.recovery;

import static fatworm.tx.recovery.LogRecord.*;

import java.util.*;

import fatworm.server.*;
import fatworm.storage.buffer.*;
import fatworm.storage.file.*;

/**
 * The recovery manager. Each transaction has its own recovery manager.
 * 
 * @author Edward Sciore
 */
public class RecoveryMgr
{
    private int txnum;

    /**
     * Creates a recovery manager for the specified transaction.
     * 
     * @param txnum
     *            the ID of the specified transaction
     */
    public RecoveryMgr(int txnum)
    {
        this.txnum = txnum;
        new StartRecord(txnum).writeToLog();
    }
    /**
     * Writes a commit record to the log, and flushes it to disk.
     */
    public void commit()
    {
        Fatworm.bufferMgr.flushAll(txnum);
        int lsn = new CommitRecord(txnum).writeToLog();
        Fatworm.logMgr.flush(lsn);
    }
    /**
     * Writes a rollback record to the log, and flushes it to disk.
     */
    public void rollback()
    {
        doRollback();
        Fatworm.bufferMgr.flushAll(txnum);
        int lsn = new RollbackRecord(txnum).writeToLog();
        Fatworm.logMgr.flush(lsn);
    }
    /**
     * Recovers uncompleted transactions from the log, then writes a quiescent
     * checkpoint record to the log and flushes it.
     */
    public void recover()
    {
        doRecover();
        Fatworm.bufferMgr.flushAll(txnum);
        int lsn = new CheckpointRecord().writeToLog();
        Fatworm.logMgr.flush(lsn);
    }
    /**
     * Writes a setint record to the log, and returns its lsn. Updates to
     * temporary files are not logged; instead, a "dummy" negative lsn is
     * returned.
     * 
     * @param buff
     *            the buffer containing the page
     * @param offset
     *            the offset of the value in the page
     * @param newval
     *            the value to be written
     */
    public int setInt(Buffer buff, int offset, int newval)
    {
        int oldval = buff.getInt(offset);
        Block blk = buff.block();
        if (isTempBlock(blk))
            return -1;
        else
            return new SetIntRecord(txnum, blk, offset, oldval).writeToLog();
    }
    /**
     * Writes a setstring record to the log, and returns its lsn. Updates to
     * temporary files are not logged; instead, a "dummy" negative lsn is
     * returned.
     * 
     * @param buff
     *            the buffer containing the page
     * @param offset
     *            the offset of the value in the page
     * @param newval
     *            the value to be written
     */
    public int setBytes(Buffer buff, int offset, byte[] newval, int len)
    {
        byte[] oldval = buff.getBytes(offset, len);
        Block blk = buff.block();
        if (isTempBlock(blk))
            return -1;
        else
            return new SetBytesRecord(txnum, blk, offset, oldval).writeToLog();
    }
    /**
     * Rolls back the transaction. The method iterates through the log records,
     * calling undo() for each log record it finds for the transaction, until it
     * finds the transaction's START record.
     */
    private void doRollback()
    {
        Iterator<LogRecord> iter = new LogRecordIterator();
        while (iter.hasNext())
        {
            LogRecord rec = iter.next();
            if (rec.txNumber() == txnum)
            {
                if (rec.op() == START)
                    return;
                rec.undo(txnum);
            }
        }
    }
    /**
     * Does a complete database recovery. The method iterates through the log
     * records. Whenever it finds a log record for an unfinished transaction, it
     * calls undo() on that record. The method stops when it encounters a
     * CHECKPOINT record or the end of the log.
     */
    private void doRecover()
    {
        Collection<Integer> finishedTxs = new ArrayList<Integer>();
        Iterator<LogRecord> iter = new LogRecordIterator();
        while (iter.hasNext())
        {
            LogRecord rec = iter.next();
            if (rec.op() == CHECKPOINT)
                return;
            if (rec.op() == COMMIT || rec.op() == ROLLBACK)
                finishedTxs.add(rec.txNumber());
            else if (!finishedTxs.contains(rec.txNumber()))
                rec.undo(txnum);
        }
    }
    /**
     * Determines whether a block comes from a temporary file or not.
     */
    private boolean isTempBlock(Block blk)
    {
        return blk.filename.startsWith("temp");
    }
}
